package de.riceman.simpleGo.main;

import javax.activation.UnsupportedDataTypeException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.richclient.application.Application;
import org.springframework.richclient.command.ActionCommand;
import org.springframework.richclient.command.TargetableActionCommand;

import de.riceman.simpleGo.SimpleGo;
import de.riceman.simpleGo.commands.HumanMoveCommand;
import de.riceman.simpleGo.events.SimpleGoLifecycleApplicationEvent;
import de.riceman.simpleGo.exceptions.IllegalMoveException;
import de.riceman.simpleGo.players.DamnStupidAI;
import de.riceman.simpleGo.players.FirstSimpleAI;
import de.riceman.simpleGo.players.HumanCLI;
import de.riceman.simpleGo.players.HumanGUI;
import de.riceman.simpleGo.players.MonteCarloSimpleAI;
import de.riceman.simpleGo.players.Player;

/**
 * The Go Game. Represents the observable model in the observer design pattern.
 * Changes are communicated via raised ApplicationEvent objects.
 * 
 * @author Riceman
 */
public class GoGame implements ApplicationListener, ApplicationContextAware {
	
	private static final Log logger = LogFactory.getLog(GoGame.class);
	
	private NewGameSetup newGameSetup = null;
	private Configuration config = null;
	private Player darkPlayer; //true
	private Player lightPlayer; //false
	private boolean continuous;
	private boolean passed = false;
	private boolean finished = false;
	
	private int moveCount = 0;


	ApplicationContext ctx;

	public GoGame() {
		if (logger.isDebugEnabled()) {
			logger.debug("GoGame object constructed.");		
		}
	}

	private void init() {
		
		int size;
		
		if (logger.isDebugEnabled()) {
			logger.debug("GoGame initialization called.");
		}
		
		continuous = newGameSetup.isContinuous();
		
		if (continuous) {
			ActionCommand cmd = (ActionCommand) Application.instance().getActiveWindow().getCommandManager().getCommand("continueCommand");
			cmd.setEnabled(false);
		}
		size = Integer.parseInt(newGameSetup.getGameSize());
		
		config = new Configuration(size);
		
		//get prepared for the daily WTF...
		//TODO: replace hard-coded types with generic approach
		if (newGameSetup.getDarkPlayersType().equals("Human via CLI")) {
	
			darkPlayer = new HumanCLI(newGameSetup.getDarkPlayersName(), true, config);
				
		} else if (newGameSetup.getDarkPlayersType().equals("DamnStupid AI")) {
			
			darkPlayer = new DamnStupidAI(newGameSetup.getDarkPlayersName(), true, config);
			
		} else if (newGameSetup.getDarkPlayersType().equals("FirstSimpleAI")) {
	
			darkPlayer = new FirstSimpleAI(newGameSetup.getDarkPlayersName(), true, config);
		} else if (newGameSetup.getDarkPlayersType().equals("MonteCarloSimpleAI")) {
	
			darkPlayer = new MonteCarloSimpleAI(newGameSetup.getDarkPlayersName(), true, config, Integer.valueOf(newGameSetup.getDarkPlayersStrength()));
		}
		
		if (newGameSetup.getLightPlayersType().equals("Human via CLI")) {
			
			lightPlayer = new HumanCLI(newGameSetup.getLightPlayersName(), false, config);
				
		} else if (newGameSetup.getLightPlayersType().equals("DamnStupid AI")) {
			
			lightPlayer = new DamnStupidAI(newGameSetup.getLightPlayersName(), false, config);
			
		} else if (newGameSetup.getLightPlayersType().equals("FirstSimpleAI")) {
	
			lightPlayer = new FirstSimpleAI(newGameSetup.getLightPlayersName(), false, config);
		} else if (newGameSetup.getLightPlayersType().equals("MonteCarloSimpleAI")) {
	
			lightPlayer = new MonteCarloSimpleAI(newGameSetup.getLightPlayersName(), false, config, Integer.valueOf(newGameSetup.getLightPlayersStrength()));
		}
		
		//if the game has been initialized, the views will be updated.
		
		if (logger.isInfoEnabled()) {
			logger.info("Firing INITIALIZED");
		}
		ctx.publishEvent(
        		new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.INITIALIZED, this));

	}
	
	/**
	 * Start.
	 */
	public void start() {
		
		Player player;
		
		try {
			Thread.sleep(300);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		if (config.getPlayersTurn()) {
			//It's the dark player's turn.
			player = darkPlayer;
			if (logger.isInfoEnabled()) {
				logger.info("Firing MOVE_EXPECTED from dark.");
			}
			
			ctx.publishEvent(
		        	new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.MOVE_EXPECTED, player));
		} else {
			//It's the light player's turn.
			player = lightPlayer;
			if (logger.isInfoEnabled()) {
				logger.info("Firing MOVE_EXPECTED from light.");
			}
			
			ctx.publishEvent(
		        	new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.MOVE_EXPECTED, player));
		}		

		moveCount++;
	}
	


	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		
		if (event instanceof SimpleGoLifecycleApplicationEvent) {
		
			SimpleGoLifecycleApplicationEvent lcae = (SimpleGoLifecycleApplicationEvent) event;
			
			if (lcae.getEventType() == SimpleGoLifecycleApplicationEvent.CREATED && lcae.objectIs(NewGameSetup.class)) {
				
				if (logger.isInfoEnabled()) {
					logger.info("SETUP DONE.");
				}
				
				//disable "New Game" menu item
				ActionCommand cmd = (ActionCommand) Application.instance().getActiveWindow().getCommandManager().getCommand("newCommand");
				cmd.setEnabled(false);
				
		    	newGameSetup = (NewGameSetup) lcae.getObject();
		    	this.init();
		    }
			
			if (lcae.getEventType() == SimpleGoLifecycleApplicationEvent.MOVE_EXPECTED && lcae.objectIs(Player.class)) {
				
				final Player currentPlayer = (Player) lcae.getObject();
				
				new Thread(
			            new Runnable() {
			                public void run() {
			                    try {
			                    	
									if (currentPlayer instanceof HumanCLI) {
										if (logger.isInfoEnabled()) {
											logger.info("GoGame received MOVE_EXPECTED, will open HumanMoveDialog.");
										}
										
										HumanMoveCommand cmd = new HumanMoveCommand();
										cmd.execute();
										
									} else {
					                	if (logger.isInfoEnabled()) {
					   						logger.info("GoGame received MOVE_EXPECTED, will generate move.");
					   					}
					                   	currentPlayer.generateMove();
									}
			                    } catch (Exception e) {
			                        e.printStackTrace();
			                    }
			                }
			            }).start();
			}
			
			if (lcae.getEventType() == SimpleGoLifecycleApplicationEvent.MOVE_GENERATED && lcae.objectIs(BitMatrix.class)) {
				
				BitMatrix move = (BitMatrix) lcae.getObject();

				if (logger.isInfoEnabled()) {
					logger.info("GoGame received MOVE_GENERATED, will check and execute move. " + move + "\n");
				}
				
				//check if player passed...
				if (move.isZeroMatrix()) {
					
					if (!passed) {
						
						//update views
						ctx.publishEvent(
				        	new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.PASSED, move));

						passed = true;
						
					//...and finish game if one has passed the move before.
					} else {
						
						finished = true;
						//update views and perform some Game Over tasks
						
						
						//re-enable "New Game" menu item
						ActionCommand cmd = (ActionCommand) Application.instance().getActiveWindow().getCommandManager().getCommand("newCommand");
						cmd.setEnabled(true);
						
						ctx.publishEvent(
					        	new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.FINISHED, this));
						//TODO: perform some Game Over tasks!
						//Game is finished here.
					}
				} else {
					passed = false;
				}
				
				if (!finished) {
					try {
						//do the move
						config.doMove(config.getPlayersTurn(), move);
						
						if (!passed) {
							
							//update views & go on
							ctx.publishEvent(
									new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.MOVED, move));
						}
						
					} catch (UnsupportedDataTypeException e) {
						e.printStackTrace();
					} catch (IllegalMoveException e) {
						moveCount--;
						ctx.publishEvent(
				        	new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.ILLEGALMOVE, this));
					}
				}
				
			}
			
			if ((lcae.getEventType() == SimpleGoLifecycleApplicationEvent.INITIALIZED && lcae.objectIs(GoGame.class)) ||
					(lcae.getEventType() == SimpleGoLifecycleApplicationEvent.MOVED && lcae.objectIs(BitMatrix.class)) ||
					(lcae.getEventType() == SimpleGoLifecycleApplicationEvent.PASSED && lcae.objectIs(BitMatrix.class))) {

				if (logger.isInfoEnabled()) {
					logger.info("GoGame received INITIALIZED/MOVED/PASSED.");
				}
				
				
				if (this.continuous) {
					new Thread(
				            new Runnable() {
				                public void run() {
				                    try {
				                    	start();                       
				                    } catch (Exception e) {
				                        e.printStackTrace();
				                    }
				                }
				            }).start();
				}
			}
			/*if (lcae.getEventType() == SimpleGoLifecycleApplicationEvent.MOVE_VIA_GUI) {
				
				if (logger.isInfoEnabled()) {
					logger.info("MOVE VIA GUI received by GoGame.");
				}

		    }*/
			
			
			 //onApplicationEvent would never be called for the following EventType, dunno why :-(
			 /*if (lcae.getEventType() == SimpleGoLifecycleApplicationEvent.CONTINUE) {
				if (logger.isInfoEnabled()) {
					logger.info("GAME CONTINUED.");
				}
				//this.start();
			}*/
		}
	}
	
	/*
	 * *******************************************
	 * GETTERS AND SETTERS
	 * *******************************************
	 */
	
	
	/**
	 * @return the configuration
	 */
	public Configuration getConfiguration() {
		return config;
	}
	
	/**
	 * @return the newGameSetup
	 */
	public NewGameSetup getPlayersConfiguration() {
		return newGameSetup;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		ctx = applicationContext;
	}
	
	/**
	 * @return the moveCount
	 */
	public int getMoveCount() {
		return moveCount;
	}

	/**
	 * @param moveCount the moveCount to set
	 */
	public void setMoveCount(int moveCount) {
		this.moveCount = moveCount;
	}
}
